#

import logging

from wglmkapplayer.writer import Writer

LOGGER = logging.getLogger("wglmkapplayer")


# MainWriter
#
class MainWriter (Writer) :


    # __init__
    #
    def __init__ (self, basename, registry) :
        super().__init__(basename)
        self.registry = registry


    # write
    #
    def write (self) :
        LOGGER.debug(f"write {self.registry} ...")

        # write files
        self.write_file_gltypes()
        self.write_file_glenums()
        self.write_file_glfuncs()

        self.write_file_applayer()
    

    # write_file_gltypes
    #
    def write_file_gltypes (self) :
        fname = self.basename + '_gltypes.inl.hpp'
        s_file = self.add_file(fname)
        s_guard = s_file.guard_section()
        self.write_gltypes(s_guard.body)
    

    # write_file_glenums
    #
    def write_file_glenums (self) :
        fname = self.basename + '_glenums.inl.hpp'
        s_file = self.add_file(fname)
        s_guard = s_file.guard_section()
        self.write_glenums(s_guard.body)
    

    # write_file_glfuncs
    #
    def write_file_glfuncs (self) :
        fname = self.basename + '_glfuncs.inl.hpp'
        s_file = self.add_file(fname)
        s_guard = s_file.guard_section()
        self.write_glfuncs(s_guard.body)
    

    # write_file_applayer
    #
    def write_file_applayer (self) :
        hname = self.basename + 'applayer.inl.hpp'
        cname = self.basename + 'applayer.inl.cpp'
        
        s_hfile = self.add_file(hname)
        s_hguard = s_hfile.guard_section()

        s_cfile = self.add_file(cname)
        
        self.write_applayer(s_hguard.body, s_cfile.root.body)


    # write_gltypes
    #
    def write_gltypes (self, s_top) :
        for type_decl in self.registry.type_decls :
            s_top.writeln(type_decl.decl)


    # write_glenums
    #
    def write_glenums (self, s_top) :
        for enum in self.registry.enums.values() :
            s_enum = s_top.section()
            s_enum.body.writeln("",
                                f"// enum {enum.name}",
                                f"//   type={enum.enum_type}",
                                f"//   group={enum.group}")
            for val in enum.values :
                if val.api != 'gl' :
                    LOGGER.warning(f"ignored: {val.name} (api={val.api})")
                    continue
                sgroups = ','.join(val.groups)
                if val.alias :
                    s_enum.body.writeln(f"// alias: {val.alias}")
                s_enum.body.writeln(f"#define {val.name:<40} ({val.value}) // type={val.value_type}, groups={sgroups}")


    # write_glfuncs
    #
    def write_glfuncs (self, s_top) :
        s_ns = s_top.section(indent_body=True, braces=True)
        s_ns.head.writeln("namespace wgl")
        
        s_table = s_ns.body.section(indent_body=True, braces='class')
        s_table.head.writeln("struct WGL_API AppLayerVTable")

        s_table.body.writeln("static AppLayerVTable & WGL_API get_current_vtable ();")

        s_table_usings = s_table.body.section()
        s_table_pointers = s_table.body.section()
        
        # marshaller usings
        marshallers = {}
        for func in self.registry.functions.values() :
            marshaller = func.marshaller
            if marshaller not in marshallers :
                pointer = func.pointer_type
                marshallers[marshaller] = True
                s_table_usings.body.writeln(f"using {marshaller} = {pointer};")
            s_table_pointers.body.writeln(f"{marshaller} {func.name};")


    ##################################################################
    

    # write_applayer
    #
    def write_applayer (self, s_hpp, s_cpp) :
        s_wglns = s_hpp.section(indent_body=True, braces=True)
        s_wglns.head.writeln("namespace wgl")
        
        # forward decls
        s_wglns.body.writeln("class WGL_API AppLayer;")

        self.write_func_infos(s_wglns.body, s_cpp)
        self.write_vtable_virtual(s_wglns.body, s_cpp)
        self.write_dynamic_layer_vtable_loader(s_wglns.body, s_cpp)


    # write_func_infos
    #
    def write_func_infos (self, s_hpp, s_cpp) : # pylint: disable=unused-argument
        s_decls = s_hpp.section()
        # sec_defs = c_top.body.section()

        infos_array_size = len(self.registry.functions) + 1
        
        s_infos_enum = s_decls.body.section(indent_body=True, braces='class')
        s_infos_enum.head.writeln("enum class AppLayerFuncID : size_t")
        s_infos_enum.body.writeln("fNone = 0,")

        s_infos_struct = s_decls.body.section(indent_body=True, braces='class')
        s_infos_struct.head.writeln("struct WGL_API AppLayerFuncInfos")
        s_infos_struct.body.writeln("AppLayerFuncID func_id;",
                                    "const char *func_name;")
        
        s_infos_array = s_decls.body.section(indent_body=True, braces='class')
        s_infos_array.head.writeln(f"static constexpr AppLayerFuncInfos APP_LAYER_FUNC_INFOS[{infos_array_size}] =")
        s_infos_array.body.writeln("{ AppLayerFuncID::fNone, NULL },",)
        
        func_id = 1
        for func in self.registry.functions.values() :
            s_infos_enum.body.writeln(f"fGl{func.name} = {func_id},")
            s_infos_array.body.writeln(f"{{ AppLayerFuncID::fGl{func.name}, \"{func.name}\" }},")
            func_id += 1


    # write_vtable_virtual
    #
    def write_vtable_virtual (self, s_hpp, s_cpp) :
        s_decls = s_hpp.section(indent_body=True, braces='class')
        s_defs = s_cpp.section()
        
        s_decls.head.writeln("struct WGL_API AppLayerVTableVirtual")
        s_decls.body.writeln("virtual void pre_call ( AppLayer &wgl_app_layer, AppLayerFuncID func_id, ... ) {}",
                             "virtual void post_call ( AppLayer &wgl_app_layer, AppLayerFuncID func_id, ... ) {}")

        for func in self.registry.functions.values() :
            # decl
            decl_proto = func.get_prototype(params_prepend=('AppLayer &wgl_app_layer',))
            s_decls.body.writeln(f"virtual {decl_proto};")

            # def
            def_proto = func.get_prototype(name_prefix="AppLayerVTableVirtual::",
                                           params_prepend=('AppLayer &wgl_app_layer',))
            s_func = s_defs.body.section(indent_body=True, braces=True)
            s_func.head.writeln(f"{def_proto}")

            callstr = f"wgl_app_layer.chain->vtable.{func.name}({func.params_names_seq});"
            if not func.is_void() :
                callstr = f"{func.return_type.decl} ret_value = {callstr}"

            hook_params = func.get_params_names_seq(prepend=("wgl_app_layer", f"AppLayerFuncID::fGl{func.name}"))
            s_func.body.writeln(f"pre_call({hook_params});",
                                callstr,
                                f"post_call({hook_params});")

            if not func.is_void() :
                s_func.body.writeln("return ret_value;")


    # write_dynamic_layer_vtable_loader
    #
    def write_dynamic_layer_vtable_loader (self, s_hpp, s_cpp) :
        s_decls = s_hpp.section(indent_body=True, braces='class')
        s_defs = s_cpp.section()

        s_decls.head.writeln("struct DynamicLayerVTableLoader")
        s_defs.body.writeln("// DynamicLayerVTableLoader functions")

        s_decls.body.writeln("static void load_vtable ( AppLayerVTable &vt );")
        s_func_load = s_defs.body.section(indent_body=True, braces=True)
        s_func_load.head.writeln("void DynamicLayerVTableLoader::load_vtable ( AppLayerVTable &vt )")

        for func in self.registry.functions.values() :
            # pylint: disable=line-too-long

            # func decl
            s_decls.body.writeln(f"static {func.prototype};")

            # func def
            proto = func.get_prototype(name_prefix="DynamicLayerVTableLoader::")
            s_func = s_defs.body.section(indent_body=True, braces=True)
            s_func.head.writeln(f"{proto}")

            callstr = f"wgl_app_layer.vtable.{func.name}({func.params_names_seq})"
            if not func.is_void() :
                callstr = "return " + callstr
            s_func.body.writeln("AppLayer &wgl_app_layer = AppLayerMaster::get_current_layer(AppLayerLoaderDynamic::LOADER_ID);",
                                f"// WGL_TRACE(\"loading GL function: gl{func.name}\");",
                                f"if (!(wgl_app_layer.vtable.{func.name} = (AppLayerVTable::{func.marshaller}) SDL_GL_GetProcAddress(\"gl{func.name}\")))",
                                f"  WGL_FATAL(\"could not load function {func.name}: %s\", SDL_GetError());",
                                f"{callstr};")

            # init vtable pointer
            s_func_load.body.writeln(f"vt.{func.name} = DynamicLayerVTableLoader::{func.name};")
